mongo: partition snapshots for numeric and string _id collections#4487
Conversation
| message StringPartitionRange { | ||
| string start = 1; | ||
| string end = 2; | ||
| } |
There was a problem hiding this comment.
we are correctly working on StringPartitionRange for MySQL and decide to have StringPartitionRange introduced an end_inclusive boolean flag for String (see #4482), bringing in a small optimization where we don't have to reserve one of the partitions for the last row/document.
| case *protos.PartitionRange_IntRange: | ||
| // Numeric _id: inclusive range. PartitionHelper builds non-overlapping | ||
| // [start, end] integer ranges. Mongo compares int32/int64 numerically, so | ||
| // int64 bounds match int32-typed _ids. | ||
| return bson.D{ | ||
| bson.E{Key: watermarkColumn, Value: bson.D{ | ||
| bson.E{Key: "$gte", Value: r.IntRange.Start}, | ||
| bson.E{Key: "$lte", Value: r.IntRange.End}, | ||
| }}, | ||
| }, nil |
There was a problem hiding this comment.
for mongo, using inclusive-inclusive int range [start, end] is prone to missing data in the rare scenario where user has doubles mixed in with integers. For example, if the _ids are 1, 2, 2.5, 3, 4; min/max will show up as 1 and 4, ended up with ranges [1, 2], [3, 4], we'd miss the 2.5 document.
we could introduce a NumericPartitionRange and use inclusive-exclusive [start, end), similar to StringPartitionRange, to avoid any gaps.
(While Mongo sorts by type so we don't have to worry about this for ObjectIDs, all numeric types are in the same type bucket)
jgao54
left a comment
There was a problem hiding this comment.
Thank you for your contribution. Using sampling for string is a great strategy for MongoDB.
Could you also add e2e tests inflow/e2e/mongo_test.go, similar to Test_Simple_Flow_Partitioned, but for string and numeric _ids for full test coverage.
| if minVal >= maxVal { | ||
| c.logger.Info("[mongo] string min/max range is non-positive, falling back to full table partition") | ||
| return fullTablePartitions(), nil | ||
| } |
There was a problem hiding this comment.
this comparison uses golang instead of db collation, so is prone to false-positive. I think we can trust the db-fetched min/max using its ordering, and just check for equality here.
| // boundaries are well distributed even with clustered keys. | ||
| stringSampleOversample = 20 | ||
| // stringSampleMaxSize caps sampling cost on very large collections. | ||
| stringSampleMaxSize = 100000 |
There was a problem hiding this comment.
today we limit numPartitions to 1000, so technically the max possible sample size is 20*1000=20000; we'll never hit this limit, but fine to keep as an upper bound
acc2b74 to
4ff837b
Compare
- Add end_inclusive to StringPartitionRange proto (aligns with MySQL PR PeerDB-io#4482 adding the same field; avoids future merge conflict) - Replace sentinel-string approach with end_inclusive=true on the last string partition, using $lte instead of $lt for its MongoDB filter - Sort string _id samples via MongoDB $sort stage instead of Go sort, preserving the collection's collation order - Filter boundary samples by exact equality instead of <=/>= comparison, trusting MongoDB's min/max guarantees - Add e2e tests for string and numeric _id partitioned snapshot flows - Document the double _id gap limitation on numericPartitions (follow-up)
7c14f4d to
24e30e2
Compare
- Add utils.FullTablePartition() helper and use it in place of the local fullTablePartitions() in qrep_partition.go and the inline construction in qrep.go - Remove slices.Compact() from computeStringBoundaries: the seen map already deduplicates interior values, and the quantile index formula is strictly increasing when len(interior) >= numPartitions, so no consecutive duplicates can occur in the else branch - Tighten e2e partition count assertions to exact counts (10) instead of >1 for string and numeric _id tests - Delete Test_Snapshot_Non_ObjectID_Falls_Back_To_Single_Partition: string _id collections are now partitioned rather than falling back to full-table, so the test expectation no longer holds
24e30e2 to
b89e2fc
Compare
|
@jgao54 Addressed comments & rebased |
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. |
Follow-ups for MongoDB parallel snapshotting #4487 to introduce NumericPartitions; this avoids missing data during initial snapshots when there are interior fractional _ids. A few other smaller clean-up, with inline comments.
Summary
Extends the MongoDB connector so initial snapshots parallelize for numeric and string
_idcollections, not just ObjectID. Previously any non-ObjectID_idfell back to a single serial full-table partition, making large naturally-keyed collections (app-store numeric IDs, package-name strings) too slow to snapshot inside the oplog retention window.Based on
v0.36.29(our deployed release).It improves initial sync (MongoDb -> Clickhouse from 10 hours to 3 hours on 8mln collection).
Approach
GetQRepPartitionsdispatches on the collection's min/max_idBSON type:utils.PartitionHelperto emit non-overlappingIntPartitionRangepartitions ($gte/$lte).$sample(honours read preference), anchored by the real min/max, emitted as a newStringPartitionRangeproto type with contiguous half-open ranges ($gte/$lt).Changes
protos/flow.proto— addStringPartitionRange+string_rangeoneof field.connectors/mongo/qrep_partition.go— type dispatcher; numeric + string builders; pure, unit-testedcomputeStringBoundaries.connectors/mongo/qrep.go—IntRange/StringRangecases intoRangeFilter.connectors/utils/monitoring/monitoring.go— handleStringRangeinaddPartitionToQRepRun(avoids theunknown range typeerror path).Testing
go build ./...,go vetclean.computeStringBoundaries(even/clustered/sparse/duplicate/out-of-range/empty + contiguity),toRangeFilter(Int/String/ObjectID/unsupported), and_idtype detection — all passing.Notes / limitations
_idand mixed-type collections fall back to full-table by design../generate-protos.sh) before building images, asflow/generatedis gitignored.